#ifndef __TCP_SERVER_HPP__
#define __TCP_SERVER_HPP__

#include "TaskPackage.hpp"
#include "baseSession.hpp"
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <thread>
#include <utility>

using boost::asio::ip::tcp;
using namespace std;

//----------------------------------------------------------------------

class participant {
public:
  virtual void show_info() {}
  uint32_t get_id() const { return uid_; }

protected:
  uint32_t uid_ = 0;
};

typedef std::shared_ptr<participant> participant_ptr;

//----------------------------------------------------------------------

class sessionManager {
public:
  void join(participant_ptr participant) {
    participants_[participant->get_id()] = participant;
  }
  void leave(participant_ptr participant) {
    participants_.erase(participant->get_id());
  }
  void show_info() {
    for (auto i : participants_) {
      i.second->show_info();
    }
  }
  std::vector<uint32_t> get_session_id_list() {
    std::vector<uint32_t> v;
    for (auto i : participants_) {
      v.push_back(i.first);
    }
    return v;
  }
  participant_ptr access_participants_by_id(uint32_t id) {
    return participants_[id];
  }

private:
  std::map<uint32_t, participant_ptr> participants_;
};

class tcpSession : public participant,
                   public std::enable_shared_from_this<tcpSession>,
                   public baseSession {
public:
  tcpSession(sessionManager &sessionManager,
             boost::asio::io_context &io_context,
             std::queue<csn::TaskPackage_ptr> &recv_pkg_que, tcp::socket socket)
      : sessionManager_(sessionManager),
        baseSession(io_context, recv_pkg_que, std::move(socket)) {
    static uint32_t uid = 0;
    uid_ = uid++;
  }
  void start() {
    sessionManager_.join(shared_from_this());
#ifdef DEBUG_INFO
    std::stringstream ss;
    ss << "session_id = " << uid_ << std::endl;
    std::cout << ss.str();
#endif
    do_read_header();
  }
  virtual void show_info() {
    cout << socket_.remote_endpoint().address().to_string() << " : "
         << socket_.remote_endpoint().port() << endl;
  }
  void write(csn::TaskPackage_ptr pkg) {
    wpkg_ = pkg;
    boost::asio::post(io_context_, [this]() { do_write(); });
  }

private:
  virtual void err_handler() {
    auto self(shared_from_this());
    sessionManager_.leave(shared_from_this());
  };
private:
  sessionManager &sessionManager_;
};

typedef std::shared_ptr<tcpSession> tcpSession_ptr;

class tcpServer {
public:
  tcpServer(boost::asio::io_context &io_context, short port)
      : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
        io_context_(io_context) {
    do_accept();
  }
  void check_clients() { sessionManager_.show_info(); }
  bool pop_pkg(csn::TaskPackage_ptr &pkg_ptr) {
    if (recv_pkg_que_.empty()) {
      return false;
    } else {
      mtx_.lock();
      pkg_ptr = recv_pkg_que_.front();
      recv_pkg_que_.pop();
      mtx_.unlock();
      return true;
    }
  }
  std::vector<uint32_t> get_session_id_list() {
    return sessionManager_.get_session_id_list();
  }
  participant_ptr get_session_by_id(uint32_t id) {
    return sessionManager_.access_participants_by_id(id);
  }

private:
  void do_accept() {
    acceptor_.async_accept(
        [this](boost::system::error_code ec, tcp::socket socket) {
          if (!ec) {
#ifdef DEBUG_INFO
            std::stringstream ss;
            ss << socket.remote_endpoint().address().to_string() << ":"
               << socket.remote_endpoint().port() << " connected" << std::endl;
            std::cout << ss.str();
#endif
            std::make_shared<tcpSession>(sessionManager_, io_context_,
                                         recv_pkg_que_, std::move(socket))
                ->start();
            do_accept();
          }
        });
  }

private:
  tcp::acceptor acceptor_;
  sessionManager sessionManager_;
  boost::asio::io_context &io_context_;
  std::queue<csn::TaskPackage_ptr> recv_pkg_que_;
  std::mutex mtx_;
};

#endif // __TCP_SERVER_HPP__
